package entity

import (
	"fmt"
	"hash/fnv"
	"sen-golang-study/go_game_study/hero_story_go_server/common"
	"sen-golang-study/go_game_study/hero_story_go_server/common/log"
	"sort"
	"sync"
	"time"
)

type ServerSelector struct {
	servers           map[uint32]*common.GameServerInfo
	responsibilityMap map[common.ServerResponsibility]map[uint32]*common.GameServerInfo
	lastHeartbeat     map[uint32]time.Time
	mutex             sync.RWMutex
	heartbeatTimeout  time.Duration
}

func NewServerSelector() *ServerSelector {
	s := &ServerSelector{
		servers:           make(map[uint32]*common.GameServerInfo),
		responsibilityMap: make(map[common.ServerResponsibility]map[uint32]*common.GameServerInfo),
		lastHeartbeat:     make(map[uint32]time.Time),
		heartbeatTimeout:  30 * time.Second,
	}

	// 初始化职责映射
	s.responsibilityMap[common.LOGIN] = make(map[uint32]*common.GameServerInfo)
	s.responsibilityMap[common.GAME] = make(map[uint32]*common.GameServerInfo)
	s.responsibilityMap[common.CHAT] = make(map[uint32]*common.GameServerInfo)
	s.responsibilityMap[common.RANK] = make(map[uint32]*common.GameServerInfo)

	// 启动心跳检测
	go s.startHeartbeatChecker()

	return s
}

func (s *ServerSelector) UpdateServer(serverInfo *common.GameServerInfo) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldServer, exists := s.servers[serverInfo.ServerId]; exists {
		s.removeFromResponsibilityMap(oldServer)
	}

	s.servers[serverInfo.ServerId] = serverInfo
	s.lastHeartbeat[serverInfo.ServerId] = time.Now()
	s.addToResponsibilityMap(serverInfo)
}

func (s *ServerSelector) RemoveServer(serverID uint32) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if server, exists := s.servers[serverID]; exists {
		s.removeFromResponsibilityMap(server)
		delete(s.servers, serverID)
		delete(s.lastHeartbeat, serverID)
	}
}

func (s *ServerSelector) SelectServer(userID uint32, responsibility common.ServerResponsibility) (*common.GameServerInfo, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	responsibilityServers, exists := s.responsibilityMap[responsibility]
	if !exists || len(responsibilityServers) == 0 {
		return nil, fmt.Errorf("没有找到能处理职责 %s 的服务器", responsibility)
	}

	var candidates []*common.GameServerInfo
	for _, server := range responsibilityServers {
		candidates = append(candidates, server)
	}

	if userID == 0 {
		return s.selectByLoad(candidates), nil
	}

	return s.selectByConsistentHash(userID, candidates), nil
}

func (s *ServerSelector) addToResponsibilityMap(serverInfo *common.GameServerInfo) {
	for _, responsibility := range serverInfo.Responsibilities {
		if responsibilityServers, exists := s.responsibilityMap[responsibility]; exists {
			responsibilityServers[serverInfo.ServerId] = serverInfo
		} else {
			s.responsibilityMap[responsibility] = map[uint32]*common.GameServerInfo{
				serverInfo.ServerId: serverInfo,
			}
		}
	}
}

func (s *ServerSelector) removeFromResponsibilityMap(serverInfo *common.GameServerInfo) {
	for _, responsibility := range serverInfo.Responsibilities {
		if responsibilityServers, exists := s.responsibilityMap[responsibility]; exists {
			delete(responsibilityServers, serverInfo.ServerId)
		}
	}
}

func (s *ServerSelector) selectByLoad(candidates []*common.GameServerInfo) *common.GameServerInfo {
	if len(candidates) == 1 {
		return candidates[0]
	}

	sort.Slice(candidates, func(i, j int) bool {
		return candidates[i].GetLoadRatio() < candidates[j].GetLoadRatio()
	})

	return candidates[0]
}

func (s *ServerSelector) selectByConsistentHash(userID uint32, candidates []*common.GameServerInfo) *common.GameServerInfo {
	if len(candidates) == 1 {
		return candidates[0]
	}

	h := fnv.New32a()
	_, err := h.Write([]byte(fmt.Sprintf("%d", userID)))
	if err != nil {
		log.Error("SelectByHash err %+v", err)
		return candidates[0]
	}
	hash := h.Sum32()

	index := hash % uint32(len(candidates))
	return candidates[index]
}

func (s *ServerSelector) startHeartbeatChecker() {
	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			s.checkHeartbeat()
		}
	}
}

func (s *ServerSelector) checkHeartbeat() {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	now := time.Now()
	var expiredServers []uint32

	for serverID, lastTime := range s.lastHeartbeat {
		if now.Sub(lastTime) > s.heartbeatTimeout {
			expiredServers = append(expiredServers, serverID)
		}
	}

	for _, serverID := range expiredServers {
		log.Info("服务器心跳超时，移除服务器: %d", serverID)
		if server, exists := s.servers[serverID]; exists {
			s.removeFromResponsibilityMap(server)
			delete(s.servers, serverID)
			delete(s.lastHeartbeat, serverID)
		}
	}
}
